package p.ithorns.tools.translator.sdk.utils;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.util.CollectionUtils;
import p.ithorns.tools.translator.annotation.Translatable;
import p.ithorns.tools.translator.sdk.core.FieldInfo;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * ReflectUtil
 *
 * @author HtL
 * @date 2025/5/20 16:19
 * @since 1.0.0
 */
public class ReflectUtil {

    private final static String BEAN_CLASS_REF = "@";

    private final static String CLASS_FIELD_REF = "$";

    private static final Cache<Class<?>, FieldInfo[]> CLASS_FIELDS_MAP = Caffeine.newBuilder()
            .initialCapacity(32)
            .maximumSize(1024)
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .build();
    private static final Cache<String, Field> FIELDS_MAP = Caffeine.newBuilder()
            .initialCapacity(32)
            .maximumSize(1024)
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .build();


    /**
     * 解析字段元数据
     *
     * @param field -字段
     * @return FieldInfo
     */
    private static FieldInfo resolveFieldInfo(Field field) {
        Annotation[] declaredAnnotations = field.getDeclaredAnnotations();
        List<Annotation> collect = Arrays.stream(declaredAnnotations)
                .filter(annotation -> isMetaAnnotation(annotation.annotationType()))
                .toList();
        if (collect.size() > 1) {
            throw new RuntimeException("Filed has two duplicate DictMeta annotations");
        }

        Annotation dictMeta = null;
        if (!CollectionUtils.isEmpty(collect)) {
            dictMeta = collect.get(0);
        }

        return new FieldInfo(field, dictMeta);
    }

    /**
     * 是否Translatable元注解, 包含继承注解
     *
     * @param candidate -候选注解类型
     */
    private static boolean isMetaAnnotation(Class<? extends Annotation> candidate) {
        if (candidate.equals(Translatable.class)) {
            return true;
        }

        Annotation[] inheritedAnnotations = candidate.getAnnotations();
        for (Annotation inheritedAnnotation : inheritedAnnotations) {
            Class<? extends Annotation> inheritedType = inheritedAnnotation.annotationType();
            if (Translatable.class.isAssignableFrom(inheritedType)) {
                return true;
            }
        }
        return false;
    }

    public static Field getClassField(final Class<?> aClass, final String fieldName) {
        String fieldFullName = aClass.getName() + CLASS_FIELD_REF + fieldName;
        return FIELDS_MAP.get(fieldFullName, name -> {
            FieldInfo[] fieldInfos = getFieldInfos(aClass);
            Map<String, Field> fieldMap = Arrays.stream(fieldInfos)
                    .map(FieldInfo::getField)
                    .collect(Collectors.toMap(Field::getName, Function.identity(), (v1, v2) -> v2));
            Field field = fieldMap.get(fieldName);
            if (null == field) {
                throw new RuntimeException("NoSuchField:" + name);
            }
            //noinspection deprecation
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            return field;
        });
    }

    /**
     * 构建对象字段引用
     * 格式 {bean}@{class}${field}
     * <p>
     * 对象唯一标识符算法：
     * 1. System.identityHashCode()：这个方法返回对象的默认哈希码，通常是对象内存地址的哈希值。
     * 不过要注意，虽然大部分情况下这个值在对象存活期间不变，但并不是绝对保证的，尤其是当对象被移动时（比如在分代GC中），
     * 但这种情况很少见，一般可以认为是不变的。
     * 2. hashCode()：hashCode()会依赖属性，属性变化，则hashCode变化。
     * 尤其需注意的是使用lombok@Data注解, 可以自动生成getter、setter、toString、equals和hashCode等方法
     * 3. 自定义唯一ID生成：比如在对象创建时生成一个唯一的ID，保存在一个final字段中，无论属性如何变化，ID都不会改变。
     */
    public static String makeBeanFieldRef(Field field, Object bean) {
        String hash = Integer.toHexString(System.identityHashCode(bean));
        String ownerFqn = field.getDeclaringClass().getName();
        String fieldName = field.getName();
        return ownerFqn + BEAN_CLASS_REF + hash + CLASS_FIELD_REF + fieldName;
    }

    public static String makeBeanFieldRef(String fieldName, Object bean) {
        String hash = Integer.toHexString(System.identityHashCode(bean));
        String ownerFqn = bean.getClass().getName();
        return ownerFqn + BEAN_CLASS_REF + hash + CLASS_FIELD_REF + fieldName;
    }


    public static String makeClassFieldName(Field field) {
        Class<?> declaringClass = field.getDeclaringClass();
        String fieldName = field.getName();
        return declaringClass.getName() + CLASS_FIELD_REF + fieldName;
    }

    /**
     * 获取字段元数据信息
     *
     * @param bean -
     * @return FieldInfo[]
     */
    public static FieldInfo[] getFieldInfos(Object bean) {
        if (null == bean) {
            return new FieldInfo[0];
        }
        Class<?> aClass = bean.getClass();
        return getFieldInfos(aClass);
    }

    /**
     * 获取字段信息(包含父类)
     *
     * @param aClass -
     * @return FieldInfo[]
     */
    private static FieldInfo[] getFieldInfos(Class<?> aClass) {
        if (null == aClass) {
            return new FieldInfo[0];
        }
        return CLASS_FIELDS_MAP.get(aClass, clazz -> {
            Field[] fields = UnSafeUtil.getAllFields(clazz);
            return Arrays.stream(fields)
                    .map(ReflectUtil::resolveFieldInfo)
                    .toArray(FieldInfo[]::new);
        });
    }

    public static FieldInfo getFieldInfo(Object bean, Field field) {
        if (null == bean) {
            return null;
        }
        Class<?> aClass = bean.getClass();
        FieldInfo[] fieldInfos = CLASS_FIELDS_MAP.getIfPresent(aClass);
        if (null == fieldInfos) {
            return null;
        }

        return Arrays.stream(fieldInfos)
                .filter(fieldInfo -> fieldInfo.getField().equals(field))
                .findFirst()
                .orElse(null);
    }

    public static <T extends Serializable> T invokeReadMethodSafe(Object bean, String fieldName) {
        return LambdaUtil.sure(() -> invokeReadMethod(bean, fieldName));
    }

    public static <T extends Serializable> T invokeReadMethodSafe(Object bean, Field field) {
        return LambdaUtil.sure(() -> invokeReadMethod(bean, field));
    }

    /**
     * 读取属性值
     */
    public static <T extends Serializable> T invokeReadMethod(Object bean, String fieldName) throws IllegalAccessException {
        assert null != bean;
        Class<?> aClass = bean.getClass();
        Field field = getClassField(aClass, fieldName);
        return (T) field.get(bean);
    }

    public static <T extends Serializable> T invokeReadMethod(Object bean, Field field) throws IllegalAccessException {
        assert null != bean;
        field.setAccessible(true);
        return (T) field.get(bean);
    }



    public static <T> void invokeWriteMethodSafe(Object bean, Field field, T parameter) {
        LambdaUtil.sure(() -> invokeWriteMethod(bean, field, parameter));
    }

    /**
     * 写入字段值
     *
     * @param bean      -
     * @param field     -
     * @param parameter -
     * @param <T>       -
     * @throws IllegalAccessException
     */
    public static <T> void invokeWriteMethod(Object bean, Field field, T parameter) throws IllegalAccessException {
        if (null != bean &&  null != field && null != parameter) {
            if (!field.getType().isAssignableFrom(parameter.getClass())) {
                throw new IllegalArgumentException("Value type does not match field type.");
            }

            field.setAccessible(true);
            field.set(bean, parameter);
        }
    }


//    public static <T> void writeFieldValue(Object bean, Field field, T parameter) throws IllegalAccessException, NoSuchFieldException {
//        if (null != parameter && !field.getType().isAssignableFrom(parameter.getClass())) {
//            throw new IllegalArgumentException("Value type does not match field type.");
//        }
//
//        Class<?> aClass = bean.getClass();
//        String fieldName = field.getName();
//        Class<?> fieldType = field.getType();
//        VarHandle varHandle = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup())
//                .findVarHandle(aClass, fieldName, fieldType);
//        varHandle.set(bean, parameter);
//    }


}